package bgpemulation;

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.Serializable;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

public class BGPGraph implements Serializable{

	private static final long serialVersionUID = 1L;
	private Map<Integer, ASNode> nodes = new HashMap<Integer, ASNode>();
	
	public BGPGraph(int asn, BGPRoutingTable rt, List<CIDRPrefix> localPrefixes){
		updateGraph(asn, rt, localPrefixes);
	}
	
	public void updateGraph(int asn, BGPRoutingTable bgpRoutingTable) {
		updateGraph(asn, bgpRoutingTable, new LinkedList<CIDRPrefix>());
	}
	
	public void updateGraph(int asn, BGPRoutingTable rt, List<CIDRPrefix> localPrefixes){
		for(Integer prefixLength : rt.prefixes.keySet()){
			for(CIDRPrefix prefix : rt.prefixes.get(prefixLength).keySet()){
				for(ASPath path : rt.prefixes.get(prefixLength).get(prefix)){
					parsePath(asn, path);
					addRoutingInfo(prefix, path);
				}
			}
		}
		addRoutingInfo(asn, localPrefixes);
	}
	
	private void addRoutingInfo(int asn, List<CIDRPrefix> localPrefixes) {
		ASPath thisAS = new ASPath("" + asn);
		for(CIDRPrefix prefix : localPrefixes){
			addRoutingInfo(prefix, thisAS);
		}
	}

	private void addRoutingInfo(CIDRPrefix prefix, ASPath path) {
		nodes.get(path.getTail()).addPrefix(prefix);
	}

	private void parsePath(int asn, ASPath path) {
		int[] asp = path.getArray();
		int e1 = asn;
		int e2 = asp[0];
		addEdge(e1, e2);
		for(int i = 1; i<asp.length; i++){
			addEdge(asp[i-1], asp[i]);
		}
	}

	public void addEdge(int a, int b){
		ASNode asa = get(a);
		ASNode asb = get(b);
		asa.addNeighbor(asb);
	}
	
	public void printGraphViz(BufferedWriter bw) throws IOException{
		bw.write("graph G{\n");
		for(ASNode node : nodes.values()){
			node.printGraphVizDec(bw);
		}
		for(ASNode node : nodes.values()){
			node.printGraphVizNeighbors(bw);
		}
		bw.write("}\n");
	}
	
	private ASNode get(int i) {
		ASNode ret = nodes.get(i);
		if(ret==null){
			ret = new ASNode(i);
			nodes.put(i, ret);
		}
		return ret;
	}

	private class ASNode implements Comparable<ASNode>, Serializable{

		private static final long serialVersionUID = 1L;
		private Integer number;
		private Set<ASNode> neighbors = new HashSet<ASNode>();
		private Set<CIDRPrefix> prefixes = new TreeSet<CIDRPrefix>();
		
		private ASNode(int number){
			this.number = number;
		}
		
		public void addPrefix(CIDRPrefix prefix) {
			prefixes.add(prefix);
		}

		private void addNeighbor(ASNode node){
			neighbors.add(node);
		}
		
		private void printGraphVizDec(BufferedWriter bw) throws IOException{
			bw.write(nodeName() + "[label=\"" + getLabel() +"\"];\n");
		}
		
		private String getLabel() {
			StringBuilder sb = new StringBuilder();
			sb.append(number);
			for(CIDRPrefix prefix : prefixes){
				sb.append("\\n" + prefix.toString());
			}
			return sb.toString();
		}

		private void printGraphVizNeighbors(BufferedWriter bw) throws IOException{
			for(ASNode node : neighbors){
				if(!(node.neighbors.contains(this) && (compareTo(node) > 0))){
					bw.write(nodeName() + "--" + node.nodeName() + ";\n");
				}
			}
		}
		
		private String nodeName(){
			return "node"+number;
		}
		
		@Override
		public boolean equals(Object o){
			if(o instanceof ASNode){
				return compareTo((ASNode)o)==0;
			} 
			return false;
		}

		@Override
		public int compareTo(ASNode arg0) {
			return number.compareTo(arg0.number);
		}
		
		@Override
		public int hashCode(){
			return number.hashCode();
		}
	}
}
